package org.jflame.auth.session.web;

import org.jflame.auth.session.SessionInfo;
import org.jflame.auth.session.SessionRegistry;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionIdListener;
import javax.servlet.http.HttpSessionListener;

import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * SessionRegistry实现,只适用于内存单机session. 摘至spring security
 * 
 * @author charles.zhang
 */
public class SessionRegistryImpl implements SessionRegistry, HttpSessionListener, HttpSessionIdListener {

    protected final Logger logger = LoggerFactory.getLogger(SessionRegistryImpl.class);
    // <principal:Object,SessionIdSet>
    private final ConcurrentMap<String,Set<String>> principals;

    // <sessionId:Object,SessionInfo>
    private final Map<String,SessionInfo> sessionIds;

    public SessionRegistryImpl() {
        this.principals = new ConcurrentHashMap<>();
        this.sessionIds = new ConcurrentHashMap<>();
    }

    public SessionRegistryImpl(ConcurrentMap<String,Set<String>> principals, Map<String,SessionInfo> sessionIds) {
        this.principals = principals;
        this.sessionIds = sessionIds;
    }

    @Override
    public List<String> getAllPrincipals() {
        return new ArrayList<>(this.principals.keySet());
    }

    @Override
    public List<SessionInfo> getAllSessions(String principal, boolean includeExpiredSessions) {
        Set<String> sessionsUsedByPrincipal = this.principals.get(principal);
        if (sessionsUsedByPrincipal == null) {
            return Collections.emptyList();
        }
        List<SessionInfo> list = new ArrayList<>(sessionsUsedByPrincipal.size());
        for (String sessionId : sessionsUsedByPrincipal) {
            SessionInfo SessionInfo = getSessionInfo(sessionId);
            if (SessionInfo == null) {
                continue;
            }
            if (includeExpiredSessions || !SessionInfo.isExpired()) {
                list.add(SessionInfo);
            }
        }
        return list;
    }

    @Override
    public SessionInfo getSessionInfo(String sessionId) {
        return this.sessionIds.get(sessionId);
    }

    @Override
    public void refreshLastRequest(String sessionId) {
        SessionInfo info = getSessionInfo(sessionId);
        if (info != null) {
            info.refreshLastRequest();
        }
    }

    @Override
    public void registerNewSession(String sessionId, String principal) {
        Validate.notEmpty(sessionId, "SessionId required as per interface contract");
        Validate.notNull(principal, "Principal required as per interface contract");
        if (getSessionInfo(sessionId) != null) {
            removeSessionInfo(sessionId);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Registering session {}, for principal {}", sessionId, principal);
        }
        this.sessionIds.put(sessionId, new SessionInfo(principal, sessionId, System.currentTimeMillis()));
        this.principals.compute(principal, (key, sessionsUsedByPrincipal) -> {
            if (sessionsUsedByPrincipal == null) {
                sessionsUsedByPrincipal = new CopyOnWriteArraySet<>();
            }
            sessionsUsedByPrincipal.add(sessionId);
            if (logger.isTraceEnabled()) {
                logger.trace("Sessions used by '{}' : {}", principal, sessionsUsedByPrincipal);
            }
            return sessionsUsedByPrincipal;
        });

    }

    @Override
    public void removeSessionInfo(String sessionId) {
        Validate.notEmpty(sessionId, "SessionId required as per interface contract");
        SessionInfo info = getSessionInfo(sessionId);
        if (info == null) {
            return;
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Removing session {} from set of registered sessions", sessionId);
        }
        this.sessionIds.remove(sessionId);
        this.principals.computeIfPresent(info.getPrincipal(), (key, sessionsUsedByPrincipal) -> {
            if (logger.isDebugEnabled()) {
                logger.debug("Removing session {} from principal's set of registered sessions", sessionId);
            }
            sessionsUsedByPrincipal.remove(sessionId);
            if (sessionsUsedByPrincipal.isEmpty()) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Removing principal {} from registry", info.getPrincipal());
                }
                sessionsUsedByPrincipal = null;
            }
            if (logger.isTraceEnabled()) {
                logger.trace("Sessions used by '{}' : {}", info.getPrincipal(), sessionsUsedByPrincipal);
            }
            return sessionsUsedByPrincipal;
        });
    }

    @Override
    public void sessionCreated(HttpSessionEvent se) {

    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        String sessionId = se.getSession()
                .getId();
        removeSessionInfo(sessionId);
    }

    @Override
    public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) {
        if (this.sessionIds.containsKey(oldSessionId)) {
            String principal = this.sessionIds.get(oldSessionId)
                    .getPrincipal();
            removeSessionInfo(oldSessionId);
            registerNewSession(event.getSession()
                    .getId(), principal);
        }

    }

}
